#include "canvas.h"
#define INITGUID
#define COBJMACROS
#include <windows.h>
#include <objbase.h>
#include <d2d1.h>
#include <d2d1helper.h>
#include <string.h>
#ifndef CANVAS_NO_DWRITE
#include <dwrite.h>
#endif
#include "winapi_adaptor.h"

#define D2D1_COLOR_SET(d2dcolor, color) \
d2dcolor.a = 1; \
d2dcolor.r = ((color & 0xff0000)>>16) / 255.0;\
d2dcolor.g = ((color & 0xff00)>>8) / 255.0;\
d2dcolor.b = ((color & 0xff)>>0) / 255.0;\

#define D2D1_RECT_SET(d2drect, x,y,w,h) \
d2drect.left = x;\
d2drect.top = y;\
d2drect.right = x+w;\
d2drect.bottom = y+h;\

#define D2D1_ROUNDED_RECT_SET(d2drect, x,y,w,h,r) \
D2D1_RECT_SET(d2drect.rect, x,y,w,h); \
d2drect.radiusX = r;\
d2drect.radiusY = r;\


#define D2D1_ELLIPSE_SET(d2drect, x,y,rx,ry) \
d2drect.point.x = x;\
d2drect.point.y = y;\
d2drect.radiusX = rx;\
d2drect.radiusY = ry;\


static const uint32_t color_black_ = 0;

struct _canvas{
    HWND hwnd;
    ID2D1RenderTarget* rt;
    ID2D1Brush* fill;
    ID2D1Brush* line;
    ID2D1StrokeStyle* sstyle;  
    IDWriteTextFormat* text_fmt; 
    int line_width; 
    int dpi;
    uint8_t text_align;
    uint8_t text_baseline;    
    uint8_t has_clip;
};



static ID2D1Factory* d2d1_factory_;
#ifndef CANVAS_NO_DWRITE
static IDWriteFactory* dwrite_factory_;
#endif
//初始化d2d1
static int init(){
    if(d2d1_factory_){
        return 0;
    }
    D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, &IID_ID2D1Factory, NULL, (void**)&d2d1_factory_);
    if(!d2d1_factory_)return -1;
#ifndef CANVAS_NO_DWRITE
    DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, &IID_IDWriteFactory, (IUnknown**)(&dwrite_factory_));
    if(!dwrite_factory_)return -1;   
#endif
    return 0;
}


static ID2D1Brush* create_solid_brush(ID2D1RenderTarget* rt, canvas_color_t c){
    uint32_t alpha = c & 0xff000000;
    ID2D1Brush* ret = NULL;
    D2D1_COLOR_F dc;
    float rate;
    D2D1_COLOR_SET(dc, c);    
    ID2D1RenderTarget_CreateSolidColorBrush(rt, &dc, NULL, (ID2D1SolidColorBrush**)&ret);
    if(ret && alpha && alpha != 0xff000000){
        rate = (float)(alpha >> 24) / 255.0;
        ID2D1Brush_SetOpacity(ret, rate);
    }
    return ret;
}

static ID2D1RenderTarget* create_hwnd_render_target(HWND hwnd){
    RECT rc;
    D2D1_HWND_RENDER_TARGET_PROPERTIES hwndprop;
    D2D1_RENDER_TARGET_PROPERTIES props;    
    HRESULT hr;
    ID2D1RenderTarget* rt = NULL;
    props.dpiX = 96;
    props.dpiY = 96;
    props.minLevel = D2D1_FEATURE_LEVEL_DEFAULT;
    props.pixelFormat.format = DXGI_FORMAT_B8G8R8A8_UNORM;
    props.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED;
    props.type = D2D1_RENDER_TARGET_TYPE_DEFAULT;
    props.usage = D2D1_RENDER_TARGET_USAGE_NONE; 
    hwndprop.hwnd = hwnd;
    GetClientRect(hwnd,&rc);
    hwndprop.pixelSize.width = rc.right;
    hwndprop.pixelSize.height = rc.bottom;
    hwndprop.presentOptions = D2D1_PRESENT_OPTIONS_NONE;      
    hr = ID2D1Factory_CreateHwndRenderTarget(d2d1_factory_, &props, &hwndprop, (ID2D1HwndRenderTarget**)&rt);
    if(FAILED(hr))return NULL;
    ID2D1RenderTarget_SetTextAntialiasMode(rt, D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE);
    return rt;
}


canvas_t canvas_create(void* native_window){
    HWND hwnd = (HWND)native_window;     
    canvas_t c = NULL;    
    init();     
    c = (canvas_t)malloc(sizeof(struct _canvas));    
    if(!c)return NULL;
    memset(c, 0, sizeof(struct _canvas));
    c->rt = create_hwnd_render_target(hwnd);    
    c->hwnd = hwnd;
    c->line_width = 1;
    c->dpi = GetDpiForWindow(hwnd);
    return c;
}


void canvas_destroy(canvas_t c){
    if(c){
        if(c->fill)ID2D1Brush_Release(c->fill);  
        if(c->line)ID2D1Brush_Release(c->line); 
        if(c->sstyle)ID2D1StrokeStyle_Release(c->sstyle);
        if(c->rt)ID2D1RenderTarget_Release(c->rt);        
        free(c);
    }
}

void canvas_resize(canvas_t c, int w, int h){
    if(c){
        if(c->rt){
            D2D1_SIZE_U sz;
            sz.width = w;
            sz.height = h;
            //int dpi = GetDpiForWindow(c->hwnd);
           // ID2D1HwndRenderTarget_SetDpi(((ID2D1HwndRenderTarget*)(c->rt)), dpi, dpi);
            ID2D1HwndRenderTarget_Resize(((ID2D1HwndRenderTarget*)(c->rt)), &sz);
        }
    }
}
void canvas_begin_draw(canvas_t c){
    if(!c || !c->rt)return;
    ID2D1RenderTarget_BeginDraw(c->rt);
}
void canvas_end_draw(canvas_t c){
    if(!c || !c->rt)return;
    if(c->has_clip){
        ID2D1RenderTarget_PopAxisAlignedClip(c->rt);
        c->has_clip = 0;
    }
    ID2D1RenderTarget_EndDraw(c->rt, NULL, NULL);
}

void canvas_clear(canvas_t c, canvas_color_t color){
    D2D1_COLOR_F dc;
    if(!c || !c->rt)return;
    D2D1_COLOR_SET(dc, color);
    ID2D1RenderTarget_Clear(c->rt, &dc);
}
void canvas_push_clip_rect(canvas_t c, int x, int y, int w, int h){
    D2D1_RECT_F rc;
    if(!c || !c->rt)return;    
    D2D1_RECT_SET(rc, x, w, w, h);
    ID2D1RenderTarget_PushAxisAlignedClip(c->rt, &rc, D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
    c->has_clip ++;
}

void canvas_pop_clip_rect(canvas_t c){    
    if(!c || !c->rt)return;
    if(c->has_clip){
        ID2D1RenderTarget_PopAxisAlignedClip(c->rt);
        c->has_clip --;
    }
}

void canvas_set_fill_color(canvas_t c, canvas_color_t color){    
    if(!c || !c->rt)return;
    if(c->fill){
        ID2D1Brush_Release(c->fill);
        c->fill = NULL;
    }    
    c->fill = create_solid_brush(c->rt, color);    
}

void canvas_set_line_width(canvas_t c, int w){
    if(!c || !c->rt)return;
    c->line_width = w;
}

void canvas_set_line_style(canvas_t c, int style){
    D2D1_STROKE_STYLE_PROPERTIES ssp;
    if(!c || !c->rt)return;
    if(c->sstyle){
        ID2D1StrokeStyle_Release(c->sstyle);
        c->sstyle = NULL;
    }
    switch(style){
        case CANVAS_LINE_STYLE_DASH:
            ssp.dashStyle = D2D1_DASH_STYLE_DASH;
            break;
        case CANVAS_LINE_STYLE_DOT:
            ssp.dashStyle = D2D1_DASH_STYLE_DOT;
            break; 
        default:
            ssp.dashStyle = D2D1_DASH_STYLE_SOLID;  
            break; 
    }
    ssp.dashCap = D2D1_CAP_STYLE_ROUND;
    ssp.dashOffset = 0;    
    ssp.endCap = 0;
    ssp.lineJoin = 0;
    ssp.miterLimit = 10.0;
    ssp.startCap = 0;
    ID2D1Factory_CreateStrokeStyle(d2d1_factory_, &ssp, NULL, 0, &c->sstyle);
}

void canvas_set_line_color(canvas_t c, canvas_color_t color){
    if(!c || !c->rt)return;
    if(c->line){
        ID2D1Brush_Release(c->line);
        c->line = NULL;
    }
    c->line = create_solid_brush(c->rt, color);  
}

//----------------------- 画框 ---------------------------
void canvas_fill_rect(canvas_t c, int x, int y, int w, int h){
    D2D1_RECT_F rc;
    if(!c || !c->rt)return;
    D2D1_RECT_SET(rc, x, y, w, h);
    if(!c->fill){
        c->fill = create_solid_brush(c->rt, color_black_);
    }
    if(!c->fill)return;
    ID2D1RenderTarget_FillRectangle(c->rt, &rc, c->fill);
}
void canvas_draw_rect(canvas_t c, int x, int y, int w, int h){
    D2D1_RECT_F rc;
    if(!c || !c->rt)return;
    D2D1_RECT_SET(rc, x, y, w, h);
    if(!c->line){
        c->line = create_solid_brush(c->rt, color_black_);
    }
    if(!c->line)return;
    ID2D1RenderTarget_DrawRectangle(c->rt, &rc, c->line, c->line_width, c->sstyle);
}
void canvas_fill_round_rect(canvas_t c, int x, int y, int w, int h, int r){
    D2D1_ROUNDED_RECT rc;
    if(!c || !c->rt)return;
    D2D1_ROUNDED_RECT_SET(rc, x, y, w, h, r);
    if(!c->fill){
        c->fill = create_solid_brush(c->rt, color_black_);
    }
    if(!c->fill)return;
    ID2D1RenderTarget_FillRoundedRectangle(c->rt, &rc, c->fill);
}
void canvas_draw_round_rect(canvas_t c, int x, int y, int w, int h, int r){
    D2D1_ROUNDED_RECT rc;
    if(!c || !c->rt)return;
    D2D1_ROUNDED_RECT_SET(rc, x, y, w, h,r);
    if(!c->line){
        c->line = create_solid_brush(c->rt, color_black_);
    }
    if(!c->line)return;
    ID2D1RenderTarget_DrawRoundedRectangle(c->rt, &rc, c->line, c->line_width, c->sstyle);    
}

void canvas_fill_ellipse(canvas_t c, int x, int y, int rx, int ry){
    D2D1_ELLIPSE rc;
    if(!c || !c->rt)return;
    D2D1_ELLIPSE_SET(rc, x, y, rx ,ry);
    if(!c->fill){
        c->fill = create_solid_brush(c->rt, color_black_);
    }
    if(!c->fill)return;
    ID2D1RenderTarget_FillEllipse(c->rt, &rc, c->fill);    
}

void canvas_draw_ellipse(canvas_t c, int x, int y, int rx, int ry){
    D2D1_ELLIPSE rc;
    if(!c || !c->rt)return;
    D2D1_ELLIPSE_SET(rc, x, y, rx ,ry);
    if(!c->line){
        c->line = create_solid_brush(c->rt, color_black_);
    }
    if(!c->line)return;
    ID2D1RenderTarget_DrawEllipse(c->rt, &rc, c->line, c->line_width, c->sstyle);     
}


//----------------------- 画线 ---------------------------

void canvas_begin_path(canvas_t c);
void canvas_close_path(canvas_t c);
void canvas_move_to(canvas_t c, int x, int y);
void canvas_line_to(canvas_t c, int x, int y);
void canvas_arc_to(canvas_t c, int x1, int y1, int x2, int y2, int radius);
void canvas_fill_path(canvas_t c);
void canvas_stroke_path(canvas_t c);
void canvas_draw_line(canvas_t c, int x1, int y1, int x2, int y2);


//----------------------- 画图 ---------------------------
canvas_image_t canvas_create_image(canvas_t c, const void * pixel, int width, int height){
    ID2D1Bitmap* bmp = NULL;
    D2D1_SIZE_U sz;
    D2D1_BITMAP_PROPERTIES props;
    uint32_t pitch;
    if(!c || !c->rt)return NULL;
    sz.height = height;
    sz.width = width;
    props.dpiX = 0;
    props.dpiY = 0;  
    props.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED;
    props.pixelFormat.format = DXGI_FORMAT_B8G8R8A8_UNORM ;
    pitch = width * 4; 
    ID2D1RenderTarget_CreateBitmap(c->rt, sz, pixel, pitch, &props, &bmp);
    return (canvas_image_t)bmp;
}
void canvas_destroy_image(canvas_image_t img){    
    if(img){
        ID2D1Bitmap_Release((ID2D1Bitmap*)img);
    }
}

void canvas_image_get_size(canvas_image_t img, int* w, int* h){
    ID2D1Bitmap* bmp = (ID2D1Bitmap*)img;
    if(!img){
        *w = 0;
        *h = 0;  
        return;      
    }
    D2D1_SIZE_U sz = ID2D1Bitmap_GetPixelSize(bmp);
    *w = sz.width;
    *h = sz.height;
}

void canvas_draw_image(canvas_t c, canvas_image_t img, int x, int y, int w, int h){
    D2D1_RECT_F rc_dst;
    if(!c || !c->rt || !img)return;
    D2D1_RECT_SET(rc_dst, x, y, w, h);
    ID2D1RenderTarget_DrawBitmap(c->rt, (ID2D1Bitmap*)img, &rc_dst, 1, D2D1_BITMAP_INTERPOLATION_MODE_LINEAR, NULL);
}

void canvas_draw_image_part(canvas_t c, canvas_image_t img, int sx, int sy, int sw, int sh, int x, int y, int w, int h){
    D2D1_RECT_F rc_dst;
    D2D1_RECT_F rc_src;
    if(!c || !c->rt || !img)return;
    D2D1_RECT_SET(rc_dst, x, y, w, h);
    D2D1_RECT_SET(rc_src, sx, sy, sw, sh);
    ID2D1RenderTarget_DrawBitmap(c->rt, (ID2D1Bitmap*)img, &rc_dst, 1, D2D1_BITMAP_INTERPOLATION_MODE_LINEAR, &rc_src);    
}


//----------------------- 画字 ---------------------------
#ifndef CANVAS_NO_DWRITE

static IDWriteTextFormat* default_font_;
//获取默认字体
canvas_font_t font_default(canvas_t c){
    LOGFONTW logfont;  
	if(SystemParametersInfoW(SPI_GETICONTITLELOGFONT, sizeof(LOGFONT), &logfont,0)){
        logfont.lfHeight = 14;
        c->dpi = GetDpiForWindow(c->hwnd);
        if(c->dpi != 96){
            float scale = c->dpi / 96;
            logfont.lfHeight *= scale;
        }
		IDWriteFactory_CreateTextFormat(dwrite_factory_, logfont.lfFaceName, NULL, DWRITE_FONT_WEIGHT_REGULAR, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, logfont.lfHeight, L"", &default_font_);        
    }
	return (canvas_font_t)default_font_;    
}

canvas_font_t canvas_create_font(canvas_t c, const char *name, int font_size, int font_style){
    IDWriteTextFormat* fmt = NULL;
    WCHAR wname[64];
    int len;
    LOGFONTW logfont;  
    DWRITE_FONT_STYLE dwstyle = DWRITE_FONT_STYLE_NORMAL;
    DWRITE_FONT_WEIGHT dwweight = DWRITE_FONT_WEIGHT_REGULAR;   
    if(!name){
        SystemParametersInfoW(SPI_GETICONTITLELOGFONT, sizeof(LOGFONT), &logfont, 0);
        memcpy(wname, logfont.lfFaceName, sizeof(logfont.lfFaceName));
    }else{
        len = MultiByteToWideChar(CP_UTF8, 0, name, -1, wname, 60);
        if(len <= 0)return font_default(c);
        wname[len] = 0;
    }
    if(font_style == CANVAS_FONT_STYLE_BOLD){
        dwweight = DWRITE_FONT_WEIGHT_BOLD;
    }
    if(font_style == CANVAS_FONT_STYLE_ITALIC){
        dwstyle = DWRITE_FONT_STYLE_ITALIC;
    }    
    IDWriteFactory_CreateTextFormat(dwrite_factory_, wname, NULL, dwweight, dwstyle, DWRITE_FONT_STRETCH_NORMAL, font_size, L"", &fmt);  
    if(!fmt)return font_default(c);
    return (canvas_font_t)fmt;
}

void canvas_destroy_font(canvas_font_t font){
    if(font && ((IDWriteTextFormat*)font != default_font_))IDWriteTextFormat_Release((IDWriteTextFormat*)font);
}

void canvas_set_font(canvas_t c, canvas_font_t font){
    if(!c || !c->rt)return;
    c->text_fmt = (IDWriteTextFormat*)font;
}
void canvas_set_text_align(canvas_t c, int text_align){
    int fmt = DWRITE_TEXT_ALIGNMENT_LEADING;
    if(!c || !c->rt)return;
    if(text_align == CANVAS_TEXT_ALIGN_CENTER){
        fmt = DWRITE_TEXT_ALIGNMENT_CENTER;
    }else if(text_align == CANVAS_TEXT_ALIGN_RIGHT){
        fmt = DWRITE_TEXT_ALIGNMENT_TRAILING;
    }
    c->text_align = fmt;
}

void canvas_set_text_baseline(canvas_t c, int text_baseline){
    int fmt = DWRITE_PARAGRAPH_ALIGNMENT_NEAR;
    if(!c || !c->rt)return;
    if(text_baseline == CANVAS_TEXT_BASELINE_BOTTOM){
        fmt = DWRITE_PARAGRAPH_ALIGNMENT_FAR;
    }else if(text_baseline == CANVAS_TEXT_BASELINE_MIDDLE){
        fmt = DWRITE_PARAGRAPH_ALIGNMENT_CENTER;
    }else if(text_baseline == CANVAS_TEXT_BASELINE_ALPHABETIC){
        fmt = DWRITE_PARAGRAPH_ALIGNMENT_CENTER;
    }  
    c->text_baseline = fmt;
}

void canvas_draw_text(canvas_t c, const char *text, int textlen, int x, int y, int w, int h){
    D2D1_RECT_F rc;
    WCHAR* wtext = NULL;
    WCHAR wbuf[256];
    int wlen,wbuflen;
    if(!c || !c->rt || !text || !textlen)return;
    if(textlen < 0)textlen = strlen(text);
    if(textlen >= 256){
        wbuflen = textlen + 1;
        wtext = (WCHAR*)malloc(wbuflen * 2);
        if(!wtext)return;
    }else{
        wbuflen = sizeof(wbuf) / 2;
        wtext = wbuf;
    }
    wlen = MultiByteToWideChar(CP_UTF8, 0, text, textlen, wtext, wbuflen);
    if(wlen <= 0)goto clean;
    wtext[wlen] = 0;

    D2D1_RECT_SET(rc, x, y, w, h);
    if(!c->text_fmt){
        c->text_fmt = (IDWriteTextFormat*)font_default(c); 
    }
    if(!c->text_fmt)goto clean;
    if(!c->fill){
        c->fill = create_solid_brush(c->rt, color_black_);
    } 
    IDWriteTextFormat_SetTextAlignment(c->text_fmt, c->text_align);
    IDWriteTextFormat_SetParagraphAlignment(c->text_fmt, c->text_baseline); 
    ID2D1RenderTarget_DrawText(c->rt, wtext, wlen, c->text_fmt, &rc, c->fill, D2D1_DRAW_TEXT_OPTIONS_NONE, DWRITE_MEASURING_MODE_NATURAL);
clean: 
    if(textlen >= 256){
        if(wtext)free(wtext);
    }  
}

bool canvas_measure_text(canvas_t c, const char *text, int textlen, int *width, int *height){
    IDWriteTextLayout* layout = NULL;
    DWRITE_TEXT_METRICS mat;
    HRESULT hr;
    WCHAR* wtext = NULL;
    WCHAR wbuf[256];
    int wlen,wbuflen;
    if(!text || !textlen){
        if(width)*width = 0;
        if(height)*height = 0;
        return true;
    }
    if(textlen < 0)textlen = strlen(text);
    if(!c || !c->rt)return false;

    if(textlen >= 256){
        wbuflen = textlen + 1;
        wtext = (WCHAR*)malloc(wbuflen * 2);
        if(!wtext)return false;
    }else{
        wbuflen = sizeof(wbuf) / 2;
        wtext = wbuf;
    }
    wlen = MultiByteToWideChar(CP_UTF8, 0, text, textlen, wtext, wbuflen);
    if(wlen <= 0)goto clean;
    wtext[wlen] = 0;
    if(!c->text_fmt){
        c->text_fmt = (IDWriteTextFormat*)font_default(c); 
    }
    if(!c->text_fmt)goto clean;  
    IDWriteFactory_CreateTextLayout(dwrite_factory_, wtext, wlen, c->text_fmt, 199999, 199999, &layout);
    if(!layout)goto clean;
    hr = IDWriteTextLayout_GetMetrics(layout, &mat); 
    if(FAILED(hr))goto clean;
    *width = mat.widthIncludingTrailingWhitespace;
    *height = mat.height; 
    IDWriteTextLayout_Release(layout);  
    if(textlen >= 256){
        if(wtext)free(wtext);
    }      
    return true;
clean: 
    if(textlen >= 256){
        if(wtext)free(wtext);
    }  
    return false;   
}

#endif
